package testcase;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;
import java.util.TimeZone;
import java.util.regex.Pattern;

import junit.framework.TestCase;
import net.fortuna.ical4j.model.*;
import net.fortuna.ical4j.model.property.ExDate;

import org.junit.After;
import org.junit.Test;

import dataobj.*;
import exceptiondates.*;
import MainComponent.ICSGenerator;

public class TestICSGenerator extends TestCase{

	class TestExceptionDates extends ExceptionDates{

		@Override
		public ArrayList<Date> getList() {
			ArrayList<Date> list = new ArrayList<>();
			try {
				list.add(df.parse("10 12 2014"));
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return new ArrayList<>();
			}
			return list;
		}

		@Override
		protected void addDates() throws ParseException {
		}
		
	}
	
	class TestCourseTO extends CourseTO{
		public String getCode(){return "e";}
		public String getSession(){return "e";}
		public String getName(){return "a";}
		public Date getStartTime() {try{return tf.parse("NOV 25, 1980 12:25 am");}catch(Exception e){return null;}}
		public Date getEndTime() {try{return tf.parse("NOV 25, 1980 7:25 pm");}catch(Exception e){return null;}}
		public String getVenue(){return "abcd";}
		public String getInstructor(){return "e";}
		public Double getCredits(){return 1.0;}
		public int getWeekDay(){return 3;}
		public Date getEndDate(){try{return tf.parse("NOV 25, 1980 11:59 pm");}catch(Exception e){return null;}}
	}
	
	private ICSGenerator generator;
	private SimpleDateFormat tf;
	private SimpleDateFormat df;
	private TestExceptionDates testED;
	private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
	
	public TestICSGenerator(){
		tf = new SimpleDateFormat("MMM dd, yyyy hh:mm a", Locale.US);
		df = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US);
		tf.setTimeZone(TimeZone.getTimeZone("Asia/Hong_Kong"));
		df.setTimeZone(TimeZone.getTimeZone("Asia/Hong_Kong"));
		testED = new TestExceptionDates();
	}

	@After
	public void cleanUp() throws Exception{
		outContent.reset();
		System.setOut(null);
	}

	
	@Override
	public void setUp(){
		generator = new ICSGenerator();
		System.setOut(new PrintStream(outContent));
	}
	
	//Test getExceptionDatesWithTime method
	@Test
	public void testExceptionDatesWithTime() throws Exception {
		//Prepare Objects
		Date courseTime = df.parse("OCT 7, 2014 23:59:52");
		Date exceptionDate = df.parse("OCT 12, 2014 21:46:12");
		DateTime result = new DateTime(df.parse("OCT 12, 2014 23:59:0"));
		
		Method m = generator.getClass().getDeclaredMethod("getExceptionDatesWithTime", java.util.Date.class, java.util.Date.class);
		m.setAccessible(true);
		assertEquals(result, m.invoke(generator, courseTime, exceptionDate));
	}

	//Test getExDateForCourse method without going through getExceptionDatesWithTime
	@Test
	public void testExDateForCourse() throws Exception {
		//Prepare Objects
		CourseTO inputCourse = new CourseTO();
		inputCourse.setStartTime(df.parse("OCT 7, 2014 23:59:52"));
		TestExceptionDates inputExceptionDate = testED;
		ExDate result = new ExDate();
		result.setTimeZone(TimeZoneRegistryFactory.getInstance().createRegistry().getTimeZone("Asia/Hong_Kong"));
		result.getDates().add(new DateTime(df.parse("OCT 12, 2014 23:59:0")));
		
		Method m = generator.getClass().getDeclaredMethod("getExDateForCourse", CourseTO.class, ExceptionDates.class);
		m.setAccessible(true);
		assertEquals(result, m.invoke(generator, inputCourse, inputExceptionDate));
	}
	
	//Test getExDateForCourse method with stub
	@Test
	public void testExDateForCourseWithStub() throws Exception {
		//Prepare Objects
		CourseTO inputCourse = new TestCourseTO();
		TestExceptionDates inputExceptionDate = testED;
		ExDate result = new ExDate();
		result.setTimeZone(TimeZoneRegistryFactory.getInstance().createRegistry().getTimeZone("Asia/Hong_Kong"));
		result.getDates().add(new DateTime(df.parse("OCT 12, 2014 00:25:0")));
		
		Method m = generator.getClass().getDeclaredMethod("getExDateForCourse", CourseTO.class, ExceptionDates.class);
		m.setAccessible(true);
		assertEquals(result, m.invoke(generator, inputCourse, inputExceptionDate));
	}
	
	//Test prepareExceptionDates method with "cityu"
	@Test
	public void testPreparingExceptionDates() throws Exception{
		String input = "cityu";
		Method m = generator.getClass().getDeclaredMethod("prepareExceptionDates", String.class);
		m.setAccessible(true);
		assertEquals(false, m.invoke(generator, input) == null);	
	}
	
	//Test prepareExceptionDates method with wrong university
	@Test
	public void testPreparingExceptionDates2() throws Exception{
		String input = "abcd";
		Method m = generator.getClass().getDeclaredMethod("prepareExceptionDates", String.class);
		m.setAccessible(true);
		assertEquals(null, m.invoke(generator, input));	
		assertEquals("FATAL ERROR: Something wrong with ExceptionDates, please contact developers to fix\n", outContent.toString());
	}
	
	//Test getRRuleWeekDay method
	
	//Everything working fine
	@Test
	public void testGetRRuleWeekDay1() throws Exception{
		int input = 1;
		Method m = generator.getClass().getDeclaredMethod("getRRuleWeekDay", Integer.class);
		m.setAccessible(true);
		assertEquals("SU", m.invoke(generator, input));	
	}
	
	//With null value
	@Test
	public void testGetRRuleWeekDay2() throws Exception{
		int input = 10;
		Method m = generator.getClass().getDeclaredMethod("getRRuleWeekDay", Integer.class);
		m.setAccessible(true);
		assertEquals(null, m.invoke(generator, input));	
	}
	
	//Test getEvent method
	
	//With everything working fine
	@Test
	public void testGetEvent1() throws Exception{
		CourseTO input = new CourseTO();
		input.setName("a");
		input.setCode("e");
		input.setSession("e");
		input.setCrn("e");
		input.setInstructor("e");
		input.setCredits(1.0);
		input.setVenue("abcd");
		input.setStartTime(tf.parse("NOV 25, 1980 12:25 am"));
		input.setEndTime(tf.parse("NOV 25, 1980 7:25 pm"));
		input.setStartDate(tf.parse("NOV 25, 1980 0:0 am"));
		input.setEndDate(tf.parse("NOV 25, 1980 11:59 pm"));
		input.setWeekDay(3);
		
		Method m = generator.getClass().getDeclaredMethod("getEvent", CourseTO.class, ExceptionDates.class);
		m.setAccessible(true);
		String pattern = "BEGIN:VEVENT\\r\\nDTSTAMP:.*\\r\\nSUMMARY:e-e - a\\r\\nDTSTART:19801125T002500\\r\\nDTEND:19801125T192500\\r\\nLOCATION:abcd\\r\\nDESCRIPTION:Instructor: e\\\\nCredits: 1.0\\r\\nEXDATE;TZID=Asia/Hong_Kong:20141012T002500\\r\\nUID:.*\\r\\nSTATUS:CONFIRMED\\r\\nRRULE:FREQ=WEEKLY;UNTIL=19801125T235900;INTERVAL=1;BYDAY=TU\\r\\nEND:VEVENT\\r\\n";
		assertEquals(Pattern.matches(pattern, m.invoke(generator, input, testED).toString()), true);
	}
	
	//With Wrong RRule Date so expected exception handling
	@Test
	public void testGetEvent2() throws Exception{
		CourseTO input = new CourseTO();
		input.setName("a");
		input.setCode("e");
		input.setSession("e");
		input.setCrn("e");
		input.setInstructor("e");
		input.setCredits(1.0);
		input.setVenue("abcd");
		input.setStartTime(tf.parse("NOV 25, 1980 12:25 am"));
		input.setEndTime(tf.parse("NOV 25, 1980 7:25 pm"));
		input.setStartDate(tf.parse("NOV 25, 1980 0:0 am"));
		input.setEndDate(tf.parse("NOV 25, 1980 11:59 pm"));
		input.setWeekDay(-1);
		
		Method m = generator.getClass().getDeclaredMethod("getEvent", CourseTO.class, ExceptionDates.class);
		m.setAccessible(true);
		assertEquals(m.invoke(generator, input, testED), null);
		assertEquals("FATAL ERROR: Something wrong with the recursion rule, please contact developers to fix it\n", outContent.toString());
	}
	
	//Test GetEvent with stub
	@Test
	public void testGetEvent3() throws Exception{
		Method m = generator.getClass().getDeclaredMethod("getEvent", CourseTO.class, ExceptionDates.class);
		m.setAccessible(true);
		String pattern = "BEGIN:VEVENT\\r\\nDTSTAMP:.*\\r\\nSUMMARY:e-e - a\\r\\nDTSTART:19801125T002500\\r\\nDTEND:19801125T192500\\r\\nLOCATION:abcd\\r\\nDESCRIPTION:Instructor: e\\\\nCredits: 1.0\\r\\nEXDATE;TZID=Asia/Hong_Kong:20141012T002500\\r\\nUID:.*\\r\\nSTATUS:CONFIRMED\\r\\nRRULE:FREQ=WEEKLY;UNTIL=19801125T235900;INTERVAL=1;BYDAY=TU\\r\\nEND:VEVENT\\r\\n";
		assertEquals(Pattern.matches(pattern, m.invoke(generator, new TestCourseTO(), testED).toString()), true);
	}
	
	//Test createICSByCourses method
	
	//With wrong ExceptionDates, expected exception handling
	@Test
	public void testCreateICSByCourses1() throws Exception{
		assertEquals(generator.createICSByCourses(new ArrayList<CourseTO>(), "test", "abcd"), false);
		assertEquals("FATAL ERROR: Something wrong with ExceptionDates, please contact developers to fix\n", outContent.toString());
	}
	
	//Everything works fine
	@Test
	public void testCreateICSByCourses2() throws Exception{
		ArrayList<CourseTO> list = new ArrayList<CourseTO>();
		CourseTO input = new CourseTO();
		input.setName("a");
		input.setCode("e");
		input.setSession("e");
		input.setCrn("e");
		input.setInstructor("e");
		input.setCredits(1.0);
		input.setVenue("abcd");
		input.setStartTime(tf.parse("NOV 25, 1980 12:25 am"));
		input.setEndTime(tf.parse("NOV 25, 1980 7:25 pm"));
		input.setStartDate(tf.parse("NOV 25, 1980 0:0 am"));
		input.setEndDate(tf.parse("NOV 25, 1980 11:59 pm"));
		input.setWeekDay(2);
		list.add(input);
		assertEquals(generator.createICSByCourses(list, "cityu", "abcd"), true);
	}
	
	//Everything works fine with stub
	@Test
	public void testCreateICSByCourses2WithStub() throws Exception{
		ArrayList<CourseTO> list = new ArrayList<CourseTO>();
		list.add(new TestCourseTO());
		assertEquals(generator.createICSByCourses(list, "cityu", "abcd"), true);
	}
}
